﻿namespace Trader.Analyzing
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Interface;

    public class Scenarios : IScenarios
    {
        private readonly IList<IRange> ranges;
        private readonly IDictionary<IScenario, int> scenarios;

        public Scenarios(decimal lowerBounds, decimal upperBounds, int numOfRanges)
        {
            ranges = new List<IRange>();
            InitializeRanges(upperBounds, lowerBounds, numOfRanges);
            scenarios = new Dictionary<IScenario, int>();
        }

        #region IScenarios Members

        public void AddEvent(IList<decimal> eventOccurance)
        {
            IScenario scenario =
                scenarios.Keys.FirstOrDefault(existingScenario => existingScenario.ExactOccurance(eventOccurance));
            if (scenario != null)
            {
                scenarios[scenario]++;
                return;
            }
            var eventRanges = new List<IRange>();
            foreach (decimal value in eventOccurance)
            {
                eventRanges.Add(ranges.First(range => range.IsInRange(value)));
            }
            scenarios.Add(new Scenario(eventRanges), 1);
        }

        public decimal? GetValuePerDay(IList<decimal> values)
        {
            int totalOccurances = 0;
            decimal totalChange = 0M;
            foreach (var scenario in scenarios)
            {
                decimal? valuePerDay = scenario.Key.GetValuePerDay(values);
                if (valuePerDay != null)
                {
                    totalOccurances += scenario.Value;
                    totalChange += valuePerDay.Value*scenario.Value;
                }
            }

            if (totalOccurances == 0)
            {
                return null;
            }

            return totalChange/totalOccurances;
        }

        public decimal GetExpectedGain()
        {
            int totalOccurances = 0;
            decimal totalChange = 0M;
            foreach (var scenario in scenarios)
            {
                decimal valuePerDay = scenario.Key.GetExpectedGain();
                totalOccurances += scenario.Value;
                totalChange += valuePerDay*scenario.Value;
            }

            return totalChange/totalOccurances;
        }

        public decimal GetMaxNegativaChange()
        {
            if (scenarios.Count == 0)
            {
                return 0;
            }

            return scenarios.Keys.Max(scenario => scenario.GetMaxNegativaChange());
        }

        public int Occurances
        {
            get { return scenarios.Values.Sum(); }
        }

        #endregion

        private void InitializeRanges(decimal upperBounds, decimal lowerBounds, int numOfRanges)
        {
            decimal rangeSize = (upperBounds - lowerBounds)/numOfRanges;
            for (int i = 0; i < numOfRanges - 1; i++)
            {
                decimal rangeLowerBounds = lowerBounds + i*rangeSize;
                decimal rangeUpperBounds = lowerBounds + (i + 1)*rangeSize;
                ranges.Add(new Range(rangeLowerBounds, rangeUpperBounds));
            }

            ranges.Add(new Range(lowerBounds + (numOfRanges - 1)*rangeSize, upperBounds));
        }
    }

    public class MaxDownChangeRiskChecker : IRiskChecker
    {
        private readonly double maxRisk;

        public MaxDownChangeRiskChecker(double maxRisk)
        {
            this.maxRisk = maxRisk;
        }

        #region IRiskChecker Members

        public bool IsNotTooRisky(IScenarios scenarios)
        {
            double risk = 1 - Math.Exp(decimal.ToDouble(scenarios.GetMaxNegativaChange())*100D/-5.77D);
            return risk <= maxRisk;
        }

        #endregion
    }
}